// $Id: array_impl_templ.hpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#ifndef XPCOM_DETAIL_ARRAY_IMPL_TEMPL_HPP
#define XPCOM_DETAIL_ARRAY_IMPL_TEMPL_HPP

#include <xpcom/config.hpp>
#include <cstring> // for std::memset
#include <algorithm>
#include <xpcom/types.hpp>
#include <xpcom/array_traits.hpp>
#include <xpcom/assert.hpp>
#include <xpcom/memory.hpp>

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_PREFIX
#endif

namespace xpcom {
namespace detail {

// Element construct/destruct
template <typename Traits>
inline void element_construct_aux(
        typename Traits::element_type* /*first*/,
        typename Traits::element_type* /*last*/,
        construct_policy::void_construct_tag)
{
    // noop
}

template <typename Traits>
inline void element_construct_aux(
        typename Traits::element_type* first,
        typename Traits::element_type* last,
        construct_policy::zero_construct_tag)
{
    XPCOM_ASSERT(first == last || (first && first <= last));
    std::memset(first, 0,
            sizeof(typename Traits::element_type) * (last - first));
}

template <typename Traits>
void element_construct_aux(
        typename Traits::element_type* first,
        typename Traits::element_type* last,
        construct_policy::ctor_construct_tag)
{
    XPCOM_ASSERT(first == last || (first && first <= last));
    while (first != last) {
        Traits::construct(*first);
        ++first;
    }
}

template <typename Traits>
inline void element_construct(
        typename Traits::element_type* first,
        typename Traits::element_type* last)
{
    typedef typename Traits::construct_policy_tag tag;
    element_construct_aux<Traits>(first, last, tag());
}

template <typename Traits>
inline void element_destruct_aux(
        typename Traits::element_type* /*first*/,
        typename Traits::element_type* /*last*/,
        destruct_policy::void_destruct_tag)
{
    // noop
}

template <typename Traits>
void element_destruct_aux(
        typename Traits::element_type* first,
        typename Traits::element_type* last,
        destruct_policy::dtor_destruct_tag)
{
    XPCOM_ASSERT(first == last || (first && first <= last));
    while (first != last) {
        Traits::destruct(*first);
        ++first;
    }
}

template <typename Traits>
inline void element_destruct(
        typename Traits::element_type* first,
        typename Traits::element_type* last)
{
    typedef typename Traits::destruct_policy_tag tag;
    element_destruct_aux<Traits>(first, last, tag());
}

#ifdef XPCOM_ENABLE_LENGTH_IS
// Unmanaged array data which honors [length_is].
template <typename T>
struct array_data_t
{
    typedef T element_type;
    typedef uint32_t size_type;

    element_type* data_;
    size_type size_;
    size_type length_;

    array_data_t() :
        data_(0),
        size_(0),
        length_(0)
    {
    }

    array_data_t(element_type* data, size_type size, size_type length) :
        data_(data),
        size_(size),
        length_(length)
    {
    }

    array_data_t(element_type* data, size_type size) :
        data_(data),
        size_(size),
        length_(size)
    {
    }

    element_type* data() const
    {
        return data_;
    }

    size_type size() const
    {
        return size_;
    }

    size_type length() const
    {
        return length_;
    }
};

// Array implementation which honors [length_is].
template <typename Traits>
class array_impl : public array_data_t<typename Traits::element_type>
{
    typedef array_data_t<typename Traits::element_type> base_type;
public:
    typedef Traits traits_type;
    typedef base_type data_type;
    typedef typename base_type::element_type element_type;
    typedef typename base_type::size_type size_type;

    using base_type::data_;
    using base_type::size_;
    using base_type::length_;

    array_impl() :
        base_type()
    {
    }

    array_impl(element_type* data, size_type size, size_type length) :
        base_type(data, size, length) // takes ownership
    {
    }

    array_impl(element_type* data, size_type size) :
        base_type(data, size) // takes ownership
    {
    }

    array_impl(array_impl& rhs) :
        base_type(rhs) // takes ownership
    {
        rhs.data_ = 0;
        rhs.size_ = 0;
        rhs.length_ = 0;
    }

    explicit array_impl(size_type n) :
        base_type()
    {
        if (n > 0) {
            data_ = memory::alloc<element_type>(n);
            element_construct<traits_type>(data_, data_ + n);
            size_ = n;
            length_ = n;
        }
    }

    ~array_impl()
    {
        if (data_) {
            element_destruct<traits_type>(data_, data_ + length_);
            memory::free(data_);
        }
    }

    element_type** data_addr()
    {
        return &data_;
    }

    void swap(array_impl& rhs)
    {
        std::swap(static_cast<base_type&>(*this), static_cast<base_type&>(rhs));
    }

    void reset()
    {
        if (data_) {
            element_destruct<traits_type>(data_, data_ + length_);
            memory::free(data_);
            data_ = 0;
            size_ = 0;
            length_ = 0;
        }
        XPCOM_ASSERT(!data_);
        XPCOM_ASSERT(size_ == 0);
        XPCOM_ASSERT(length_ == 0);
    }

    void release(element_type** data, size_type* size, size_type* length)
    {
        XPCOM_ASSERT(data);
        XPCOM_ASSERT(size != 0);
        XPCOM_ASSERT(length != 0);

        *data = data_;
        *size = size_;
        *length = length_;

        data_ = 0;
        size_ = 0;
        length_ = 0;
    }

    void release(element_type** data, size_type* size)
    {
        XPCOM_ASSERT(data);
        XPCOM_ASSERT(size != 0);

        *data = data_;
        *size = length_;

        data_ = 0;
        size_ = 0;
        length_ = 0;
    }

    void reserve(size_type n)
    {
        if (n > size_) {
            data_ = memory::realloc(data_, n);
            size_ = n;
        }
    }

    void resize(size_type n)
    {
        if (n < length_) {
            XPCOM_ASSERT(data_);
            element_destruct<traits_type>(data_ + n, data_ + length_);
            length_ = n;

        } else if (n > length_) {
            reserve(n);
            XPCOM_ASSERT(data_);
            element_construct<traits_type>(data_ + length_, data_ + n);
            length_ = n;
        }
    }

private:
    const array_impl& operator=(const array_impl&);
};

#else // XPCOM_ENABLE_LENGTH_IS
// Unmanaged array data which ignores [length_is].
template <typename T>
struct array_data_t
{
    typedef T element_type;
    typedef uint32_t size_type;

    element_type* data_;
    size_type size_;

    array_data_t() :
        data_(0),
        size_(0)
    {
    }

    array_data_t(element_type* data, size_type /*size*/, size_type length) :
        data_(data),
        size_(length)
    {
    }

    array_data_t(element_type* data, size_type size) :
        data_(data),
        size_(size)
    {
    }

    element_type* data() const
    {
        return data_;
    }

    size_type size() const
    {
        return size_;
    }

    size_type length() const
    {
        return size_;
    }
};

// Array implementation which ignores [length_is].
template <typename Traits>
class array_impl : public array_data_t<typename Traits::element_type>
{
    typedef array_data_t<typename Traits::element_type> base_type;
public:
    typedef Traits traits_type;
    typedef base_type data_type;
    typedef typename base_type::element_type element_type;
    typedef typename base_type::size_type size_type;

    using base_type::data_;
    using base_type::size_;

    array_impl() :
        base_type()
    {
    }

    array_impl(element_type* data, size_type size, size_type length) :
        base_type(data, size, length) // takes ownership
    {
    }

    array_impl(element_type* data, size_type size) :
        base_type(data, size) // takes ownership
    {
    }

    array_impl(array_impl& rhs) :
        base_type(rhs) // takes ownership
    {
        rhs.data_ = 0;
        rhs.size_ = 0;
    }

    explicit array_impl(size_type n) :
        base_type()
    {
        if (n > 0) {
            data_ = memory::alloc<element_type>(n);
            element_construct<traits_type>(data_, data_ + n);
            size_ = n;
        }
    }

    ~array_impl()
    {
        if (data_) {
            element_destruct<traits_type>(data_, data_ + size_);
            memory::free(data_);
        }
    }

    element_type** data_addr()
    {
        return &data_;
    }

    void swap(array_impl& rhs)
    {
        std::swap(static_cast<base_type&>(*this), static_cast<base_type&>(rhs));
    }

    void reset()
    {
        if (data_) {
            element_destruct<traits_type>(data_, data_ + size_);
            memory::free(data_);
            data_ = 0;
            size_ = 0;
        }
        XPCOM_ASSERT(!data_);
        XPCOM_ASSERT(size_ == 0);
    }

    void release(element_type** data, size_type* size, size_type* length)
    {
        XPCOM_ASSERT(data);
        XPCOM_ASSERT(size != 0);
        XPCOM_ASSERT(length != 0);

        *data = data_;
        *size = size_;
        *length = size_;

        data_ = 0;
        size_ = 0;
    }

    void release(element_type** data, size_type* size)
    {
        XPCOM_ASSERT(data);
        XPCOM_ASSERT(size != 0);

        *data = data_;
        *size = size_;

        data_ = 0;
        size_ = 0;
    }

    void reserve(size_type n)
    {
        if (n > size_) {
            data_ = memory::realloc(data_, n);
            //size_ = n;
        }
    }

    void resize(size_type n)
    {
        if (n < size_) {
            // XXX do not realloc data here, it is just OK
            XPCOM_ASSERT(data_);
            element_destruct<traits_type>(data_ + n, data_ + size_);
            size_ = n;

        } else if (n > size_) {
            data_ = memory::realloc(data_, n);
            XPCOM_ASSERT(data_);
            element_construct<traits_type>(data_ + size_, data_ + n);
            size_ = n;
        }
    }

private:
    const array_impl& operator=(const array_impl&);
};

#endif // XPCOM_ENABLE_LENGTH_IS

} // namespace xpcom::detail
} // namespace xpcom

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_SUFFIX
#endif

#endif // XPCOM_DETAIL_ARRAY_IMPL_TEMPL_HPP
